/* 
 *	Copyright 2014  Yanqing Wu
 *		email: yqwu_yqwu@126.com
 *
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
*/

#include "stdafx.h"
#include "StdCtrlSkinBgCell.h"

#include "StdCtrlSkinTool_Utils.h"
#include <atlimage.h>

bool CStdCtrlSkinBgCell::LoadXmlNode(CComPtr<IXMLDOMNode> spNode)
{
	auto attribPairs = StdCtrlSkinTool_XmlGetAttribPairs(spNode);
	auto pEnum = StdCtrlSkinTool_GetEnum();
	for(auto i : attribPairs)
	{
		auto name = i.first;
		auto value = i.second;
		CStdCtrlSkin_EnumAttrib attr;
		bool correct = pEnum->Parse(attr, name);
		if ( !correct )
			continue;
		switch(attr)
		{
		case CStdCtrlSkin_EnumAttrib::NAME:
			ctrl_id = value;
			break;
		case CStdCtrlSkin_EnumAttrib::WIDTH:
			attrib.width = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::HEIGHT:
			attrib.height = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::PADX:
			attrib.padx = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::PADY:
			attrib.pady = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::STICKY:
			pEnum->ParseCombo(attrib.sticky, value);
			break;
		case CStdCtrlSkin_EnumAttrib::ROWSPAN:
			rowspan = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::COLSPAN:
			colspan = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::TYPE:
			pEnum->Parse(bgAttrib.type, value);
			break;
		case CStdCtrlSkin_EnumAttrib::COLOR1:
			bgAttrib.color1 = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::COLOR2:
			bgAttrib.color2 = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::ALPHA:
			bgAttrib.alpha = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::DIRECTION:
			pEnum->Parse(bgAttrib.direction, value);
			break;
		case CStdCtrlSkin_EnumAttrib::FILEPATH:
			bgAttrib.filepath = value;
			break;
		case CStdCtrlSkin_EnumAttrib::DRAWMODE:
			pEnum->Parse(bgAttrib.drawmode, value);
			break;
		case CStdCtrlSkin_EnumAttrib::TRANS:
			bgAttrib.trans = StdCtrlSkinTool_CString2Long(value);
			bgAttrib.bUseTrans = true;
			break;
		case CStdCtrlSkin_EnumAttrib::SRCORIGIN:
			pEnum->ParseCombo(bgAttrib.srcorigin, value);
			break;
		case CStdCtrlSkin_EnumAttrib::BBOX:
			bgAttrib.bShowBox = true;
			bgAttrib.boxcolor = StdCtrlSkinTool_CString2Long(value);
			break;
		default:
			break;
		}
	}
	return true;
}

void CStdCtrlSkinBgCell::Draw(HDC hDC)
{
	auto rect = GetCtrlRect();
	auto width = rect.Width(), height = rect.Height();
	HBITMAP hBmp = CreateCompatibleBitmap(hDC, width, height);
	HDC hSrc = CreateCompatibleDC(hDC);
	HBITMAP hOld = (HBITMAP)SelectObject(hSrc, hBmp);
	if ( bgAttrib.bShowBox )
	{
		auto cellRect = GetCellRect();
		HPEN hPen = CreatePen(PS_SOLID, 1, bgAttrib.boxcolor);
		HBRUSH hBrush = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
		HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);
		HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
		Rectangle(hDC, cellRect.left, cellRect.top, cellRect.right, cellRect.bottom);
		SelectObject(hDC, hOldPen);
		SelectObject(hDC, hOldBrush);
		DeleteObject(hPen);
	}
	CString filepath;
	CImage* pImg;
	CRect rectNew(0, 0, rect.Width(), rect.Height());
	switch(bgAttrib.type)
	{
	case CStdCtrlSkin_EnumBgType::GRADIENT:
		DrawGradient(hSrc, width, height);
		break;
	case CStdCtrlSkin_EnumBgType::IMAGE:
		filepath = GetFilePath();
		filepath += bgAttrib.filepath;
		pImg = StdCtrlSkinTool_LoadImageFile(filepath);
		if ( !pImg )
			return;
		if ( pImg->GetBPP()==32 )
		{
			DrawImage(hDC, rect, pImg);
			delete pImg;
			SelectObject(hSrc, hOld);
			DeleteObject(hBmp);
			DeleteDC(hSrc);
			return;
		}
		DrawImage(hSrc, rectNew, pImg);
		break;
	default:
		return;
	}
	rect.left += rectNew.left;
	rect.right = rect.left + rectNew.Width();
	rect.top += rectNew.top;
	rect.bottom = rect.top + rectNew.Height();
	if ( bgAttrib.bUseTrans )
		DoTransparent(hDC, hSrc, rectNew, rect);
	else if ( bgAttrib.alpha )
		DoAlphaBlend(hDC, hSrc, rectNew, rect);
	else
		DoBmpCopy(hDC, hSrc, rectNew, rect);
	SelectObject(hSrc, hOld);
	DeleteObject(hBmp);
	DeleteDC(hSrc);
}

void CStdCtrlSkinBgCell::DrawGradient(HDC hDC, int width, int height)
{
	if ( bgAttrib.color1 == bgAttrib.color2 )
	{
		//	Same color, just fill a rectangle
		HBRUSH hBrush = CreateSolidBrush(bgAttrib.color1);
		HPEN hPen = (HPEN)GetStockObject(NULL_PEN);
		HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
		HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);
		Rectangle(hDC, 0, 0, width, height);
		SelectObject(hDC, hOldBrush);
		SelectObject(hDC, hOldPen);
		DeleteObject(hBrush);
	}
	TRIVERTEX vert[3];
	GRADIENT_RECT gRect;
	GRADIENT_TRIANGLE gTri;
	//bgAttrib.color1 = RGB(0x80, 0x80, 0x80);
	//bgAttrib.color2 = RGB(0x40, 0x40, 0x40);
	COLOR16 r1 = GetRValue(bgAttrib.color1) << 8, g1 = GetGValue(bgAttrib.color1) << 8, b1 = GetBValue(bgAttrib.color1) << 8;
	COLOR16 r2 = GetRValue(bgAttrib.color2) << 8, g2 = GetGValue(bgAttrib.color2) << 8, b2 = GetBValue(bgAttrib.color2) << 8;
	auto r3 = (r1+r2)/2, g3 = (g1+g2)/2, b3=(b1+b2)/2;
	switch(bgAttrib.direction)
	{
	case CStdCtrlSkin_EnumGradDir::HORIZONTAL:
	case CStdCtrlSkin_EnumGradDir::VERTICAL:
		vert[0].Red = r1;
		vert[0].Green = g1;
		vert[0].Blue = b1;
		vert[0].Alpha = bgAttrib.alpha;
		vert[0].x = 0;
		vert[0].y = 0;
		vert[1].Red = r2;
		vert[1].Green = g2;
		vert[1].Blue = b2;
		vert[1].Alpha = bgAttrib.alpha;
		vert[1].x = width;
		vert[1].y = height;
		gRect.UpperLeft = 0;
		gRect.LowerRight = 1;
		if ( bgAttrib.direction==CStdCtrlSkin_EnumGradDir::HORIZONTAL)
			GradientFill(hDC, vert, 2, &gRect, 1, GRADIENT_FILL_RECT_H);
		else
			GradientFill(hDC, vert, 2, &gRect, 1, GRADIENT_FILL_RECT_V);
		break;
	case CStdCtrlSkin_EnumGradDir::SLASH:
		//	Upper right triangle
		vert[0].Red = r1;
		vert[0].Green = g1;
		vert[0].Blue = b1;
		vert[0].Alpha = bgAttrib.alpha;
		vert[0].x = width;
		vert[0].y = 0;
		vert[1].Red = r3;
		vert[1].Green = g3;
		vert[1].Blue = b3;
		vert[1].Alpha = bgAttrib.alpha;
		vert[2] = vert[1];
		vert[1].x = 0;
		vert[1].y = 0;
		vert[2].x = width;
		vert[2].y = height;
		gTri.Vertex1 = 0;
		gTri.Vertex2 = 1;
		gTri.Vertex3 = 2;
		GradientFill(hDC, vert, 3, &gTri, 1, GRADIENT_FILL_TRIANGLE);
		//	Lower left triangle
		vert[0].Red = r2;
		vert[0].Green = g2;
		vert[0].Blue = b2;
		vert[0].x = 0;
		vert[0].y = height;
		GradientFill(hDC, vert, 3, &gTri, 1, GRADIENT_FILL_TRIANGLE);
		break;
	case CStdCtrlSkin_EnumGradDir::BACKSLASH:
		//	Upper left triangle
		vert[0].Red = r1;
		vert[0].Green = g1;
		vert[0].Blue = b1;
		vert[0].Alpha = bgAttrib.alpha;
		vert[0].x = 0;
		vert[0].y = 0;
		vert[1].Red = r3;
		vert[1].Green = g3;
		vert[1].Blue = b3;
		vert[1].Alpha = bgAttrib.alpha;
		vert[2] = vert[1];
		vert[1].x = width;
		vert[1].y = 0;
		vert[2].x = 0;
		vert[2].y = height;
		gTri.Vertex1 = 0;
		gTri.Vertex2 = 1;
		gTri.Vertex3 = 2;
		GradientFill(hDC, vert, 3, &gTri, 1, GRADIENT_FILL_TRIANGLE);
		//	Lower right triangle
		vert[0].Red = r2;
		vert[0].Green = g2;
		vert[0].Blue = b2;
		vert[0].x = width;
		vert[0].y = height;
		GradientFill(hDC, vert, 3, &gTri, 1, GRADIENT_FILL_TRIANGLE);
		break;
	default:
		break;
	}
}

void CStdCtrlSkinBgCell::DrawImage(HDC hDC, CRect& rect, CImage* pImg)
{
	switch( bgAttrib.drawmode )
	{
	case CStdCtrlSkin_EnumImgMode::CENTER:
		DrawImageCenter(hDC, rect, pImg);
		break;
	case CStdCtrlSkin_EnumImgMode::STRETCH:
		DrawImageStretch(hDC, rect, pImg);
		break;
	case CStdCtrlSkin_EnumImgMode::TILE:
		DrawImageTile(hDC, rect, pImg);
		break;
	default:
		break;
	}
}

void CStdCtrlSkinBgCell::DrawImageCenter(HDC hDC, CRect& rect, CImage* pImg)
{
	auto width=rect.Width(), height=rect.Height();
	auto bmpW = pImg->GetWidth(), bmpH = pImg->GetHeight();
	//	Default size to copy, assume the cell is larger than bmp
	int src_x = 0, src_y = 0, copyW = bmpW, copyH = bmpH;
	int dst_x = 0, dst_y = 0;

	if ( width < bmpW )
	{
		//	 Check the src origin. sticky priority is w > e > c
		copyW = width;
		if ( (int)bgAttrib.srcorigin & (int)CStdCtrlSkin_EnumSticky::W )
		{
		}
		else if ( (int)bgAttrib.srcorigin & (int)CStdCtrlSkin_EnumSticky::E )
			src_x = bmpW - width;
		else
			src_x = (bmpW - width) / 2;
	}
	else
	{
		//	image is smaller, check sticky of source
		if ( (int)attrib.sticky & (int)CStdCtrlSkin_EnumSticky::W )
			rect.right = rect.left + bmpW;
		else if ( (int)attrib.sticky & (int)CStdCtrlSkin_EnumSticky::E )
			rect.left = rect.right - bmpW;
		else
		{
			rect.left += (width - bmpW) / 2;
			rect.right = rect.left + bmpW;
		}
	}

	if ( height < bmpH )
	{
		//	check the src origin. sticky priority is n > s > c
		copyH = height;
		if ( (int)bgAttrib.srcorigin & (int)CStdCtrlSkin_EnumSticky::N )
		{}
		else if ( (int)bgAttrib.srcorigin & (int)CStdCtrlSkin_EnumSticky::S )
			src_y = bmpH - height;
		else
			src_y = (bmpH - height) / 2;
	}
	else
	{
		if ( (int)attrib.sticky & (int)CStdCtrlSkin_EnumSticky::N )
			rect.bottom = rect.top + bmpH;
		else if ( (int)attrib.sticky & (int)CStdCtrlSkin_EnumSticky::S )
			rect.top = rect.bottom - bmpH;
		else
		{
			rect.top += (height - bmpH) / 2;
			rect.bottom = rect.top + bmpH;
		}
	}
	pImg->Draw(hDC, rect.left, rect.top, copyW, copyH, src_x, src_y, copyW, copyH);
}

void CStdCtrlSkinBgCell::DrawImageStretch(HDC hDC, CRect rect, CImage* pImg)
{
	pImg->Draw(hDC, rect.left, rect.top, rect.Width(), rect.Height());
}

void CStdCtrlSkinBgCell::DrawImageTile(HDC hDC, CRect rect, CImage* pImg)
{
	//	The pattern brush does not support transparent png, let's try
	//	drawing the image one by one
	auto imgW = pImg->GetWidth(), imgH  = pImg->GetHeight();
	for(int i=rect.left; i<rect.right; i+=imgW)
		for(int j=rect.top; j<rect.bottom; j+=imgH)
		{
			int width, height;
			width = min(imgW, rect.right-i);
			height = min(imgH, rect.bottom-j);
			pImg->Draw(hDC, i, j, width, height, 0, 0, width, height);
		}
}

void CStdCtrlSkinBgCell::DoTransparent(HDC hDC, HDC hSrc, CRect rectSrc, CRect rectDst)
{
	auto width = rectDst.Width(), height = rectDst.Height();
	TransparentBlt(hDC, rectDst.left, rectDst.top, width, height, hSrc, rectSrc.left, rectSrc.top, width, height, bgAttrib.trans);
}

void CStdCtrlSkinBgCell::DoAlphaBlend(HDC hDC, HDC hSrc, CRect rectSrc, CRect rectDst)
{
	auto width = rectDst.Width(), height = rectDst.Height();
	BLENDFUNCTION ftn;
	ftn.BlendOp = AC_SRC_OVER;
	ftn.BlendFlags = 0;
	ftn.SourceConstantAlpha = 255 - bgAttrib.alpha;
	ftn.AlphaFormat = 0;
	AlphaBlend(hDC, rectDst.left, rectDst.top, width, height, hSrc, rectSrc.left, rectSrc.top, width, height, ftn);
}

void CStdCtrlSkinBgCell::DoBmpCopy(HDC hDC, HDC hSrc, CRect rectSrc, CRect rectDst)
{
	auto width = rectDst.Width(), height = rectDst.Height();
	BitBlt(hDC, rectDst.left, rectDst.top, width, height, hSrc, rectSrc.left, rectSrc.top, SRCCOPY);
}

#ifdef __DUMPTEST
void CStdCtrlSkinBgCell::Dump(std::ofstream& fio, CStdCtrlSkin_EnumDump type)
{
	CStdCtrlSkinCell::Dump(fio, type);
	if ( !((int)type & (int)CStdCtrlSkin_EnumDump::CELL_ATTRIB_INST) )
		return;
	//	Dump bgAttrib
	auto pEnum = StdCtrlSkinTool_GetEnum();
	auto typeStr = pEnum->ToString(bgAttrib.type);
	CStringA typeStrA(typeStr);
	fio << "\ttype=" << typeStrA.GetString();
	if ( bgAttrib.type==CStdCtrlSkin_EnumBgType::IMAGE )
	{
		auto mode = pEnum->ToString(bgAttrib.drawmode);
		auto origin = pEnum->ComboToString(bgAttrib.srcorigin);
		CString tranS;
		tranS.Format(_T("%x"), bgAttrib.trans);
		CStringA modeA(mode), originA(origin), filepathA(bgAttrib.filepath), transA(tranS);
		fio << " filepath=" << filepathA.GetString() << " drawmode=" << modeA.GetString()
			<< " usetrans=" << bgAttrib.bUseTrans << " transparent=" << transA.GetString()
			<< " srcorigin=" << originA.GetString() << std::endl;
	}
	else
	{
		auto dir = pEnum->ToString(bgAttrib.direction);
		CStringA dirA(dir);
		CString color1S, color2S;
		color1S.Format(_T("%x"), bgAttrib.color1);
		color2S.Format(_T("%x"), bgAttrib.color2);
		CStringA color1A(color1S), color2A(color2S);
		fio << " dir=" << dirA.GetString() << " color1=" << color1A.GetString()
			<< " color2=" << color2A.GetString() << " alpha=" << (int)bgAttrib.alpha
			<< std::endl;
	}
}
#endif